feat(protocol): 7.15 release additions — hive signing (1614-1617), clearsign identity icons, thorchain denom, ripple memo, zcash - #112
Conversation
- PCZT streaming protocol: ZcashSignPCZT, ZcashPCZTAction, ZcashPCZTActionAck - Orchard FVK: ZcashGetOrchardFVK, ZcashOrchardFVK - Transparent shielding: ZcashTransparentInput, ZcashTransparentSig - Wire IDs 1300-1307 - nanopb options for all fields Multi-phase protocol: session init → action streaming → transparent signing. Supports on-device Orchard digest verification via sub-digest fields.
New messages for displaying a Zcash unified address on the device screen with FVK verification. The host provides the UA string and FVK components; the device independently derives FVK from seed and verifies the match before displaying the address with a QR code. - ZcashDisplayAddress (wire_in 1308): address + ak/nk/rivk for verification - ZcashAddress (wire_out 1309): confirmed address after user approval - nanopb options: address max_size:128, key fields max_size:32
feat(zcash): add ZcashDisplayAddress protocol (IDs 1308/1309)
The device only verifies the Orchard FVK — it cannot verify transparent or Sapling receivers that may also be bundled in a Unified Address. Updated proto comments to explicitly state the guarantee: "This UA contains an Orchard receiver from this account" rather than implying full address ownership. Also clarified that account or address_n is required (no silent fallback to account 0).
docs(zcash): clarify ZcashDisplayAddress verification scope
…TIP-712) Adds proto definitions for TRON message-signing parity: - TronSignMessage / TronMessageSignature (1404/1405) — TIP-191 personal_sign - TronVerifyMessage (1406) — host-asserted signature verification - TronSignTypedHash / TronTypedDataSignature (1407/1408) — TIP-712 hash mode Mirrors the Ethereum personal_sign + EIP-712 hash-mode shape. Firmware implementation will reuse the secp256k1 + keccak256 primitives already present for Ethereum, swapping the message prefix to '\x19TRON Signed Message:\n' for TIP-191 and using '\x19\x01' for TIP-712. Reserves IDs 1404-1408 contiguous to existing TRON range (1400-1403).
Adds TonSignMessage / TonMessageSignature (1504/1505) — basic Ed25519 arbitrary-bytes signing, mirroring SolanaSignMessage's shape. This primitive lacks domain separation by design (raw Ed25519 over message bytes). Firmware should gate it behind the AdvancedMode policy — same fence used for SolanaSignMessage in fsm_msg_solana.h — until a TON Connect ton_proof envelope is added as a separate proto. Reserves IDs 1504-1505 contiguous to existing TON range (1500-1503).
Adds SolanaSignOffchainMessage / SolanaOffchainMessageSignature (756/757) implementing the Solana off-chain message spec: '\xff' || 'solana offchain' || version || format || length || message The '\xff' lead byte is invalid as a Solana transaction prefix, providing the domain separation that plain SolanaSignMessage (754/755) lacks. With this primitive, firmware can drop the AdvancedMode policy gate currently required for SolanaSignMessage (fsm_msg_solana.h:461-472) for ASCII/UTF8 off-chain messages, since the envelope makes transaction-shaped attacks impossible. message_format values per spec: 0 = Restricted ASCII (max 1212 bytes) — display-renderable 1 = UTF-8 limited (max 1212 bytes) — display-renderable with care 2 = UTF-8 extended (max 65515) — blind-sign only Reserves IDs 756-757 contiguous to existing Solana range (750-755). Bumped message max_size to 1212 to match the spec ceiling for formats 0/1.
…sages
ZIP-32 §6.1 seed fingerprint:
SeedFingerprint := BLAKE2b-256("Zcash_HD_Seed_FP", seed)
A 32-byte stable identity of the device's seed. Adds optional bytes
seed_fingerprint fields across the existing zcash messages so hosts
and devices can bind FVKs, addresses, and signing sessions to a
specific seed identity.
Four new fields, all optional, fully backward compatible:
ZcashOrchardFVK.seed_fingerprint (4)
Returned alongside (ak, nk, rivk). Lets a host pin an FVK to
this device's seed.
ZcashAddress.seed_fingerprint (2)
Returned alongside the confirmed UA after on-device verification.
Lets a host record "this address is on this device's seed."
ZcashSignPCZT.expected_seed_fingerprint (31)
Sent by host. If present, device checks against its own
fingerprint and rejects with Failure on mismatch before signing.
Mirrors Keystone3's PCZT zip32_derivation seed_fingerprint check
at the session level (one tx = one seed, no per-action duplication
needed for our flow).
ZcashDisplayAddress.expected_seed_fingerprint (7)
Sent by host. Same rejection semantics as above before displaying.
Matching nanopb max_size:32 entries added to messages-zcash.options.
No existing fields modified. Devices and hosts that don't populate
the new fields continue to work unchanged.
feat(zcash): add seed_fingerprint binding to FVK / address / sign messages
Brings in upstream's 7.14.0 release + features/7.15 commits: d0b8d80 feat: 7.14.0 protocol — BIP-85, EVM metadata, Solana, TRON, TON, Zcash 18bb4a7 Merge pull request #100 from keepkey/release/7.14.0 bbcfcb0 feat: add ZcashDisplayAddress protocol messages (IDs 1308-1309) bf8646b Merge pull request #101 from keepkey/features/7.15 Note: ZcashDisplayAddress was developed in parallel on both sides. Conflict resolution prefers upstream's version (max_size:256 for the unified-address fields, more correct than fork's :128 since UAs can be long when shielded receivers are present). # Conflicts: # messages-zcash.options # messages-zcash.proto # messages.proto # package.json
Brings in TRON/TON/Solana message-signing proto definitions: 0e3dc97 feat(tron): TIP-191 SignMessage, VerifyMessage, TIP-712 SignTypedHash 20e646a feat(ton): Ed25519 SignMessage primitive c0ef415 feat(solana): SignOffchainMessage with domain-separated envelope After this merge, fork master is the single source of truth that firmware branches pin to during fork-only testing. A clean upstream PR can be assembled later by cherry-picking these 3 commits onto a fresh branch off keepkey/master.
Field comments documented the formula as
BLAKE2b-256("Zcash_HD_Seed_FP", seed)
but ZIP-32 §6.1 (and the actual conforming implementations in the
upstream zip32 Rust crate, keystone3-firmware, and our own firmware)
prepend a 1-byte length:
BLAKE2b-256("Zcash_HD_Seed_FP", I2LEBSP_8(len(seed)) || seed)
A host implementer following the proto comments would compute the
wrong fingerprint and have the device reject every signing/display
request with "seed fingerprint mismatch."
Comment-only change. No wire-format impact.
…formula-doc fix(zcash): correct seed_fingerprint formula in proto comments
Remove fields 3-6 (address, ak, nk, rivk) from ZcashDisplayAddress. Field numbers are reserved to prevent reuse. The on-device UA derivation (Sinsemilla + SWU hash-to-curve) shipped — FVK-match attestation against a host-built UA is strictly weaker than device-derived display and is no longer supported. What stays: address_n / account / expected_seed_fingerprint. What ZcashAddress returns: address (now device-derived) + seed_fingerprint.
Add optional string memo field (field 7) to RippleSignTx protobuf message. This enables THORChain swap routing memos and other arbitrary memo data to be included in XRP transactions signed by the device.
feat(ripple): add memo field to RippleSignTx for 7.14.2
Merges upstream 7.14.1 (Tron TIP-191/712, TON SignMessage, Solana SignOffchainMessage) with fork's Zcash UA/seed_fingerprint/display address additions. Conflict resolution: kept upstream's format-2 removal from SolanaSignOffchainMessage docs.
Adds messages-hive.proto with HiveGetPublicKey, HivePublicKey, HiveSignTx, and HiveSignedTx. Assigns message type IDs 1600-1603 in messages.proto. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* release: device-protocol 7.14.1 * feat(tron): add SignMessage (TIP-191), VerifyMessage, SignTypedHash (TIP-712) Adds proto definitions for TRON message-signing parity: - TronSignMessage / TronMessageSignature (1404/1405) — TIP-191 personal_sign - TronVerifyMessage (1406) — host-asserted signature verification - TronSignTypedHash / TronTypedDataSignature (1407/1408) — TIP-712 hash mode Mirrors the Ethereum personal_sign + EIP-712 hash-mode shape. Firmware implementation will reuse the secp256k1 + keccak256 primitives already present for Ethereum, swapping the message prefix to '\x19TRON Signed Message:\n' for TIP-191 and using '\x19\x01' for TIP-712. Reserves IDs 1404-1408 contiguous to existing TRON range (1400-1403). * feat(ton): add SignMessage Ed25519 message-signing primitive Adds TonSignMessage / TonMessageSignature (1504/1505) — basic Ed25519 arbitrary-bytes signing, mirroring SolanaSignMessage's shape. This primitive lacks domain separation by design (raw Ed25519 over message bytes). Firmware should gate it behind the AdvancedMode policy — same fence used for SolanaSignMessage in fsm_msg_solana.h — until a TON Connect ton_proof envelope is added as a separate proto. Reserves IDs 1504-1505 contiguous to existing TON range (1500-1503). * feat(solana): add SignOffchainMessage with domain-separated envelope Adds SolanaSignOffchainMessage / SolanaOffchainMessageSignature (756/757) implementing the Solana off-chain message spec: '\xff' || 'solana offchain' || version || format || length || message The '\xff' lead byte is invalid as a Solana transaction prefix, providing the domain separation that plain SolanaSignMessage (754/755) lacks. With this primitive, firmware can drop the AdvancedMode policy gate currently required for SolanaSignMessage (fsm_msg_solana.h:461-472) for ASCII/UTF8 off-chain messages, since the envelope makes transaction-shaped attacks impossible. message_format values per spec: 0 = Restricted ASCII (max 1212 bytes) — display-renderable 1 = UTF-8 limited (max 1212 bytes) — display-renderable with care 2 = UTF-8 extended (max 65515) — blind-sign only Reserves IDs 756-757 contiguous to existing Solana range (750-755). Bumped message max_size to 1212 to match the spec ceiling for formats 0/1. * feat(zcash): drop host-supplied UA from ZcashDisplayAddress Remove fields 3-6 (address, ak, nk, rivk) from ZcashDisplayAddress. Field numbers are reserved to prevent reuse. The on-device UA derivation (Sinsemilla + SWU hash-to-curve) shipped — FVK-match attestation against a host-built UA is strictly weaker than device-derived display and is no longer supported. What stays: address_n / account / expected_seed_fingerprint. What ZcashAddress returns: address (now device-derived) + seed_fingerprint. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * docs: update SolanaSignOffchainMessage to reflect 1212 byte limit and drop format 2 Agent-Logs-Url: https://github.com/keepkey/device-protocol/sessions/880cd954-b4b2-4f87-af05-8d715e1e0dc4 Co-authored-by: pastaghost <62026038+pastaghost@users.noreply.github.com> * feat(hive): add Hive blockchain message definitions Adds messages-hive.proto with HiveGetPublicKey, HivePublicKey, HiveSignTx, and HiveSignedTx. Assigns message type IDs 1600-1603 in messages.proto. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(hive): add messages-hive.proto to build, drop broken build:json step build:json used pbjs v0.0.5 which cannot parse proto3 reserved fields (present in zcash, cosmos, ethereum, etc). proto.json is unused by the vault — only messages_pb.js is imported. Build now runs build:js + build:postprocess only. * feat(hive): add HiveGetPublicKeys, HiveSignAccountCreate, HiveSignAccountUpdate + SLIP-0048 paths --------- Co-authored-by: pastaghost <62026038+pastaghost@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
NearGetAddress, NearAddress, NearSignTx, NearSignedTx. Ed25519 derivation m/44'/397'/0'. Implicit account = lowercase hex of the 32-byte Ed25519 pubkey. Numbered 1610-1613 to avoid colliding with Hive (1600-1609), which landed on alpha after the original NEAR spike.
feat(near): NEAR Protocol proto definitions (MessageType 1610-1613)
…oadcast (#35) account_create is authorized on-chain by the creator (sponsor) account's active authority, not by the new account. The device's owner-key signature here is a proof-of-control attestation; the sponsor recovers the owner pubkey to verify control, then rebuilds and signs the real account_create with the creator's active key server-side. Comment-only — no wire or generated-code change. Addresses review P1 on the (closed) feature/hive PR.
Adds the device-protocol messages required by the upcoming firmware release:
- thorchain: ThorchainMsgSend.denom (field 11) — non-RUNE assets (TCY, RUJI, IBC)
- ripple: RippleSignTx.memo (field 7) — XRP->THORChain swap routing
- hive: full Hive support — HiveGetPublicKey(s), HiveSignTx,
HiveSignAccountCreate/Update (MessageType 1600-1609)
- zcash: clear-signing + Orchard shielded protocol (transparent in/out/ack,
PCZT, FVK, display-address)
All additions are new optional fields / new message types — additive and
backward-compatible. lib/ bindings are gitignored build artifacts; package.json
build:js/json updated to include messages-hive.proto.
…t/taproot-capability-staging
…taging feat(features): advertise Taproot signing support
ResetDevice.dice_entropy=10 asks the device to collect dice rolls with the single button and mix them into the internal entropy before it is displayed or committed. DebugLinkDecision.input=2 lets debug builds inject synthetic roll input (kept short so the decoded struct fits the firmware tiny-message buffer). DebugLinkState.dice_digest=15 exposes SHA-256 of the collected ASCII roll string so tests can prove the device received exactly the injected rolls. ButtonRequest_DiceRoll=39 announces the entry screen to the host.
feat(reset): dice-entropy fields for on-device roll collection
Picks up f2c3c00 (ci: auto-request Copilot review), the only commit the fork lacked. The fork's 28 protocol commits -- zcash Orchard + clear-sign, hive, near, tron/ton/solana message signing, ripple memo, thorchain denom -- are unaffected; this adds one workflow file and nothing else.
yarn install --frozen-lockfile fails on this branch with: @mapbox/node-pre-gyp@2.0.3: The engine "node" is incompatible with this module. Expected version ">=18". Got "17.2.0" The unpinned legacy circleci/node image resolves to node 17.2.0. master passes only because its older lockfile predates that transitive requirement; any lockfile refresh trips it. Pin a maintained image.
The Ironwood enum was inserted above the file-level java_package / java_outer_classname options, leaving them after a top-level definition. protobufjs's parser rejects that outright: Error: illegal token 'option' (messages-zcash.proto, line 16) so 'npm run build' (build:json -> lib/proto.json) fails on this branch. protoc accepts either order, so the generated descriptors are unchanged; every other proto in the repo already declares options directly after syntax. Verified: pbjs now emits proto.json cleanly.
alpha pinned device-protocol at cf308fd, 32 commits ahead of fork master and 29 behind. Per the branch SOP alpha must pin fork MASTERS, not loose commits -- a pin that exists only on a feature branch is the tech debt, because nothing downstream can resolve it without knowing which branch it came from. Merged rather than picked, since both lines had real work: messages.proto NEAR 1610-1613 (master) + Hive 1614-1617 and Clearsign attestor 1700-1703 (alpha). No ID collision -- alpha's side already carried the comment reserving 1610-1613 for NEAR, so this merge was anticipated. messages-zcash alpha's shielded_pool and ironwood_digest (NU6.3 / v6) messages-hive alpha's SignMessage/SignedMessage options; master's account_create attestation docs kept package.json alpha's scripts as the base: they actually run build:json, and use grpc_tools_node_protoc explicitly rather than bare protoc. Master contributed messages-near.proto, which alpha lacked. All three of near/zcash/hive now appear in both build:js and build:json. Verified no duplicate field numbers within any message, and no duplicate MessageType IDs.
A Solana v0 transaction may source instruction accounts from an Address Lookup
Table. Those accounts are NOT in the bytes being signed, so the device cannot
derive them and refuses to guess: the transaction is forced to
SOL_TX_REVIEW_OPAQUE, which fsm_msg_solana.h refuses outright unless the user
has enabled AdvancedMode, and which is then an explicit BLIND SIGN. The
instruction's meaning is never shown.
So this does not rescue a blank screen -- nothing is signed silently. It
upgrades a BLIND SIGN into a provider-attested CLEAR SIGN, which is the whole
point of the tier.
KKSOLSC1 schemas cannot close this: they are instruction-scoped and reusable,
carry no transaction hash, and work by decoding values out of the bytes the
device is signing. With a lookup table those bytes do not contain the accounts.
So a provider attests the resolved account list for THIS transaction:
preimage = "KeepKeySolanaTxAccounts/1" || message_hash(32)
|| count(le32) || account[i](32) ...
Bound to the exact message hash, so it cannot be replayed onto another
transaction. Domain-tagged, so a signature made for any other purpose -- an EVM
metadata blob, a token definition -- cannot be replayed as one.
Annotation, not authority: accounts render as PROVIDER-ATTESTED next to the
provider alias, and the unverified-transaction review still runs.
Uses tags 5-7 of the reservation that named this descriptor. Tag 8 stays
reserved for one-request opaque-signing consent.
feat(solana): KKSOLSW1 transaction-bound account attestation on tags 5-7
Five messages (1704-1708) that let the device drive an EIP-712 walk instead of
being handed the document.
The device asks for one type definition, or one leaf VALUE, at a time, and
hashes each value in the same pass that displays it. The host owns the
document; the device holds only the digest stack for the containers currently
open. A 10,000-element array costs the same RAM as a 2-element one, and there
is no document-size limit to raise later.
This replaces Ethereum712TypesValues, which shipped the whole thing as two
2048-byte JSON blobs and was withdrawn in 7.14.2 because its parser could not
guarantee the value on screen was the value being hashed. Here that is
structural rather than reviewed: a value is displayed and absorbed from the
same buffer in the same call, and each member_path is requested exactly once.
That last clause is not incidental. Trezor shipped this same protocol with a
hole until 2.12.0 -- nothing bound repeated answers for one path to each other,
so a host could answer the domain name one way for the summary screen and
another for the hashing pass. Requesting each path once closes it by
construction rather than by caching around it.
ONE deliberate divergence from Trezor and OneKey: arrays.
Both describe an array as a field whose entry_type is another EthereumFieldType
-- a self-referential message. Trezor can, because core is Python. OneKey does
it on nanopb by compiling that one field as a POINTER (PB_ENABLE_MALLOC) and
then flattening the pointer chain into a fixed pool to sever the recursion.
KeepKey's nanopb is static-allocation only, and a heap inside a signing device
is not a liability worth taking on for one field. So array nesting is FLATTENED
onto the wire the way Ledger describes it: data_type is always the LEAF type,
and array_levels carries the dimensions in written order, 0 for dynamic:
uint256 -> UINT, size=32, array_levels=[]
address[] -> ADDRESS, array_levels=[0]
Person[3] -> STRUCT, struct_name="Person", array_levels=[3]
int16[2][][4] -> INT, size=2, array_levels=[2,0,4]
Nothing EIP-712 permits is lost and the encoding is bounded, flat and
statically sized. Enum values still match Trezor's so a shared host keeps its
mapping; ARRAY is reserved and never sent.
Values arrive as raw big-endian bytes of the declared width, not JSON. That
deletes the whole decimal-parsing step from the device -- and with it the
64-bit integer ceiling that made the old path refuse an unlimited approval,
which is the most common permit there is.
Validated with protoc 3.5.1 in kktech/firmware:v8.
…aming feat(eip712): device-driven field streaming for structured typed data
up/release-protocol carried two build fixes that exist only on this
upstream branch -- the cimg/node:20.11 CI pin and moving the zcash file
options into the preamble so pbjs can parse them -- while 34 commits of
7.15 protocol work sat on the fork's master. develop cannot pin the fork
commit, because a PR into upstream carries its submodule pins and a
reviewer cannot resolve one that exists only on a fork.
Merged rather than rebased so nothing is force-pushed over an open PR,
and so both histories stay readable.
Verified after the merge, because a clean auto-merge keeps hunks from
both sides and that is exactly how one side quietly loses:
- cimg/node:20.11 still pinned in .circleci/config.yml
- the zcash file options are still in the preamble
- EthereumSignTypedData / EthereumTypedDataStructAck /
EthereumTypedDataValueAck all present in messages-ethereum.proto
keepkey/python-keepkey#216 -- the 7.14.2 disclosure tests -- is merged, so upstream master is 598097e1 and carries them. This PR pinned 87ff2499, that PR's branch head, which was the right pin while it was open and is the wrong one now: a merged PR's branch can be deleted, and a pin at a branch head rather than at master is exactly what a reviewer cannot resolve later. 598097e1 is 1 ahead of 87ff2499 and 0 behind -- the merge commit, nothing else. Every submodule pin on this branch was re-verified to FETCH from its real URL, not merely to be well-formed. deps/device-protocol stays at f2c3c005, which IS upstream master: the 7.14.2 line needs no protocol change. The 7.15 protocol additions are keepkey/device-protocol#112 and land with that release.
Review guideScope: the 7.15 protocol additions. This is a dependency merge — nothing here ships to a device by itself; it makes submodule pins resolvable for the firmware releases that follow. The change is wire-additiveThe only deletion in - MessageType_ZcashTransparentSig = 1307 [(wire_out) = true];
+ MessageType_ZcashTransparentSigned = 1307 [(wire_out) = true];Identical wire value, so nothing on the wire breaks. The other deletions are in Where the volume actually is+3,071/−112 across 22 files sounds large; only ~750 lines are real
Worth checking
Why it matters nowTwo firmware releases are waiting on this pin becoming resolvable on
Both |
There was a problem hiding this comment.
Pull request overview
Adds firmware 7.15 protocol surfaces across Hive, Zcash, clearsigning, and several chains, plus validation and binding-generation updates.
Changes:
- Adds Hive, NEAR, Zcash, Ethereum/Solana clearsigning, Ripple, and THORChain protocol fields.
- Updates JavaScript protobuf generation and dependencies.
- Adds protocol CI and Zcash contract validation.
Reviewed changes
Copilot reviewed 19 out of 22 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
yarn.lock |
Updates generator dependencies. |
types.proto |
Adds dice-roll button type. |
tools/check_zcash_contract.py |
Validates the RC18 Zcash contract. |
package.json |
Updates protobuf builds and inputs. |
package-lock.json |
Locks updated npm dependencies. |
messages.proto |
Registers messages and common fields. |
messages-zcash.proto |
Defines revised Zcash signing flows. |
messages-zcash.options |
Bounds Zcash fields. |
messages-thorchain.proto |
Adds send denomination. |
messages-solana.proto |
Adds clearsigning attestations. |
messages-solana.options |
Bounds new Solana fields. |
messages-ripple.proto |
Adds transaction memo. |
messages-near.proto |
Defines NEAR protocol messages. |
messages-hive.proto |
Defines Hive protocol messages. |
messages-hive.options |
Bounds Hive fields. |
messages-ethereum.proto |
Adds clearsign and EIP-712 messages. |
messages-ethereum.options |
Bounds new Ethereum fields. |
lib/messages-ripple_pb.js |
Adds generated Ripple bindings. |
lib/messages-ripple_pb.d.ts |
Adds Ripple TypeScript declarations. |
.github/workflows/copilot-review.yml |
Supports cross-fork review requests. |
.github/workflows/ci.yml |
Adds protocol validation CI. |
.circleci/config.yml |
Updates the Node.js image. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- ci: include messages-near.proto in the descriptor-validation protoc run so the shipped NEAR schema is actually compiled, matching both package builds - solana: bound the KKSOLSW1 LUT fields (lut_account, lut_signature) and the SolanaTokenInfo attestation signature in messages-solana.options, so nanopb emits fixed-size fields instead of callbacks - zcash: document the Zcash wire v2 breaking migration at the top of messages-zcash.proto (1307 rename and reshape, transparent output/ack phase, ZcashDisplayAddress 3-6 removal, compact signatures, sapling_digest) - near: mark 1610-1613 and messages-near.proto as schema-only and not implemented by firmware 7.15/RC18, add messages-near.options, and move the Hive 1614-1617 IDs out from under the NEAR comment - hive: drop the duplicated legacy paragraph on HiveSignAccountCreate that contradicted the attestation contract
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 23 changed files in this pull request and generated 1 comment.
Suppressed comments (5)
Previously missed (4) — in code that hasn't changed since the last review.
.github/workflows/ci.yml:36
- This only checks the
preparelifecycle'sbuild:jsoutput. It never runs the modifiedbuild:jsoncommand or theprepublishOnlybuild path, so CI can remain green even when the publishable package cannot producelib/proto.jsonor complete post-processing. Run the fullnpm run buildin CI and validate its outputs; the current BSD-stylesed -i ''postprocess also needs to be made Linux-compatible for this runner.
- name: Verify install generated JavaScript bindings
run: |
test -s lib/messages_pb.js
test -s lib/messages-solana_pb.js
test -s lib/messages-zcash_pb.js
messages-zcash.proto:77
- The linked RC18 firmware hard-caps Orchard actions at 16 (
ZCASH_MAX_ACTIONS), but this public request field documents no limit andmessages-zcash.optionsstill allocates up to 64 returned signatures. A host following these bindings can construct a 17–64 action request that the release rejects, while nanopb consumers generated here allocate a different response shape. Document the 16-action limit and align the options bound with the released implementation.
optional uint32 n_actions = 4; // Number of Orchard actions to stream and validate
messages-solana.proto:44
- This documentation does not match the linked RC18 firmware: when no signature is supplied, firmware still uses a safe unsigned symbol for amount formatting while displaying the authenticated mint separately; only a present-but-invalid signature causes the symbol to be ignored. Document the actual trust/display behavior so host implementers do not incorrectly assume unsigned symbols are never shown.
// Optional attestation: ECDSA(secp256k1) signature over a domain-separated
// digest of (mint, decimals, symbol), signed by a clear-sign signer the
// user loaded via LoadClearsignSigner. When present and valid, the device
// trusts the symbol; when absent it falls back to displaying the raw mint.
// (Firmware verifies; the host/SDK signing side is a follow-up.)
messages.proto:113
- This introduces a new multi-message structured EIP-712 signing API, but the PR description does not mention or validate it. The diff also adds clearsign-attestor message types, Solana attestation/schema fields, taproot capability signaling, and dice-entropy/debug fields outside the stated Hive/clearsign-icon/THORChain/Ripple/Zcash scope. Update the PR scope and validation evidence so these security-sensitive signing surfaces receive explicit review rather than being treated as incidental release additions.
MessageType_EthereumSignTypedData = 1704 [ (wire_in) = true ];
MessageType_EthereumTypedDataStructRequest = 1705 [ (wire_out) = true ];
MessageType_EthereumTypedDataStructAck = 1706 [ (wire_in) = true ];
MessageType_EthereumTypedDataValueRequest = 1707 [ (wire_out) = true ];
MessageType_EthereumTypedDataValueAck = 1708 [ (wire_in) = true ];
messages-hive.proto:122
- The request contract immediately above says this result is proof-of-control attestation material and is not broadcastable, but this response description again calls it a signed account-create transaction. That wording can lead consumers to submit the returned bytes as an on-chain transaction.
* Response: Signed Hive account_create transaction
The Ed25519 signature covers only SHA256(raw_tx), so the host-supplied receiver_id and action_display were never bound to the bytes being signed. A malicious host could display a benign receiver and action while obtaining a signature over entirely different transaction bytes. Reserve tags 4 and 5 so no implementation can reintroduce them, and state the confirmation contract on the message: every value shown to the user is derived by the device from raw_tx, and a transaction the device cannot parse must be refused rather than displayed from host-provided text. A Borsh NearTransaction already carries signer_id, public_key, nonce, receiver_id, block_hash and the action list inside the signed bytes.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 23 changed files in this pull request and generated no new comments.
Suppressed comments (8)
Previously missed (6) — in code that hasn't changed since the last review.
package.json:13
- The new generator emits a multiline bootstrap containing
return Function('return this')()(see the generated Ripple binding), whilebuild:postprocessstill matches only the oldvar global = Function(...)form. The replacement therefore no longer runs, leaving the dynamic-function fallback in published bindings. Update the postprocessor to match the new output.
"build:js": "mkdir -p ./lib && ./node_modules/.bin/grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --js_out=import_style=commonjs,binary:./lib --ts_out=./lib types.proto messages.proto messages-ethereum.proto messages-eos.proto messages-nano.proto messages-cosmos.proto messages-binance.proto messages-ripple.proto messages-tendermint.proto messages-thorchain.proto messages-osmosis.proto messages-mayachain.proto messages-solana.proto messages-tron.proto messages-ton.proto messages-zcash.proto messages-hive.proto messages-near.proto",
package.json:14
prepareruns for Git-based installs, but it generates only JavaScript. Becauselib/is gitignored andproto.jsonis produced only bybuild:json, those installs omit the JSON descriptor that published packages contain. Generate both artifact sets here.
"prepare": "npm run build:js",
messages.proto:113
- This introduces a separate five-message structured EIP-712 signing protocol that is absent from the PR title, additions list, and validation summary. The same diff also adds attestor, Solana-attestation, dice-entropy, taproot, and Ironwood surfaces. These security-sensitive public APIs materially exceed the described 7.15 scope; update the PR description and validation plan or split the unrelated surfaces so reviewers can assess the actual release contract.
MessageType_EthereumSignTypedData = 1704 [ (wire_in) = true ];
MessageType_EthereumTypedDataStructRequest = 1705 [ (wire_out) = true ];
MessageType_EthereumTypedDataStructAck = 1706 [ (wire_in) = true ];
MessageType_EthereumTypedDataValueRequest = 1707 [ (wire_out) = true ];
MessageType_EthereumTypedDataValueAck = 1708 [ (wire_in) = true ];
messages-zcash.proto:99
- Fields 19–20 advertise Ironwood/transaction-v6 selection, but the stated RC18 contract is Orchard-only and the linked exact firmware candidate's Zcash request metadata and digest API contain neither
shielded_poolnorironwood_digest. A host selecting Ironwood therefore cannot receive the behavior this schema implies. Remove/reserve these fields until firmware support exists, or explicitly mark them schema-only/rejected and pin that behavior in the contract check.
// NU6.3 / transaction-v6 Orchard-family pool selection. The existing
// orchard_* metadata fields describe the selected action bundle for wire
// compatibility; ironwood_digest is the fifth v6 transaction component.
optional ZcashShieldedPool shielded_pool = 19 [default = ZCASH_SHIELDED_POOL_ORCHARD];
optional bytes ironwood_digest = 20; // 32-byte Ironwood component digest (v6)
messages-hive.options:16
- Nanopb
max_sizefor strings includes the terminating NUL, so these values accept only 15-character names. Hive permits 16-character account names; valid senders or recipients at that limit cannot be encoded by the generated firmware structs.
This issue also appears in the following locations of the same file:
- line 26
- line 39
HiveSignTx.from max_size:16
HiveSignTx.to max_size:16
lib/messages-ripple_pb.js:1
lib/is explicitly ignored as generated output (.gitignore:2) and is regenerated bypackage.json:11. Committing only this Ripple artifact creates a second, stale-prone source of truth; remove it from the PR and generate bindings during packaging/install.
// source: messages-ripple.proto
messages-hive.options:27
- These account-name buffers also allow only 15 characters because the configured size includes the NUL terminator. A valid 16-character creator or new account name is rejected/truncated; allocate 17 bytes.
HiveSignAccountCreate.creator max_size:16
HiveSignAccountCreate.new_account_name max_size:16
messages-hive.options:39
- This buffer cannot represent Hive's valid 16-character account names because
max_size:16includes the trailing NUL. Increase it to 17.
HiveSignAccountUpdate.account max_size:16
Hive account names are 3-16 characters (Graphene fixed_string_16), but nanopb max_size includes the NUL terminator, so max_size:16 only admits 15 characters. A valid 16-character account name was truncated or rejected by the generated firmware structs. Size the five account-name fields at 17: HiveSignTx.from, HiveSignTx.to, HiveSignAccountCreate.creator/new_account_name, and HiveSignAccountUpdate.account. RC18 is Orchard-only, but ZcashSignPCZT tags 19-20 advertised Ironwood / transaction-v6 pool selection that no firmware in this release implements, so a host selecting Ironwood could not receive the behavior the schema implied. Mark ZCASH_SHIELDED_POOL_IRONWOOD and ironwood_digest SCHEMA ONLY on their declarations, document that 7.15/RC18 rejects a non-Orchard pool and a present ironwood_digest with Failure, and pin both markers plus the field declarations in tools/check_zcash_contract.py so the schema-only status cannot silently drift into an implied guarantee.
Upstream branch → upstream master.
keepkey:up/release-protocolis the single protocol PR for the firmware 7.15 / RC18 release line. It replaces the mis-structured #111 and remains blocked on human review.Additions
All changes outside Zcash are additive; existing field numbers and wire behavior remain compatible. The Zcash surface is a breaking change — see "Zcash wire protocol v2" below.
HiveSignMessage/HiveSignedMessage(1614/1615) andHiveSignOperations/HiveSignedOperations(1616/1617).1610-1613are allocated to NEAR as schema only:messages-near.protoandmessages-near.optionsdefine the intended wire surface, but firmware 7.15 / RC18 ships no NEAR handler and answers those requests withFailure_UnexpectedMessage. Hosts must not advertise NEAR support on the basis of these bindings.LoadClearsignSigner: bounded RLE icon data,icon_width,icon_height, and the legacy-compatiblepersistfield.Zcash wire protocol v2 (BREAKING)
Zcash shielded support shipped as a preview with no stable host implementations, so the flow was corrected in place rather than duplicated behind new message IDs. A host written against firmware <= 7.14.x will not interoperate with 7.15 and must be updated. The full migration is documented at the top of
messages-zcash.proto:1307renamedZcashTransparentSig->ZcashTransparentSignedand reshaped from one signature per request to a batchedrepeated bytes signatures. The wire ID is unchanged, so a v1 host decodes1307into the wrong message.ZcashTransparentOutput(1310) andZcashTransparentAck(1311). Outputs stream before inputs and all inputs are buffered before any signature, because ZIP-244 per-input transparent sighashes commit to every transparent prevout, value, script, sequence and output.ZcashTransparentInput.sighashis demoted to optional and rejected when present.ZcashDisplayAddressfields 3-6 (address,ak,nk,rivk) removed and reserved; the device derives and displays its own Orchard UA. Eitheraccountor a fulladdress_nis now required.ZcashPCZTAction.is_spendis required, andZcashSignedPCZT.signaturesis compact — a v1 host indexing signatures by action index reads the wrong signature.ZcashSignPCZT.sapling_digestis reserved for future Sapling support and rejected when set.RC18 Zcash wire contract
The regular/full RC18 firmware includes Orchard privacy. Only
bitcoin-onlycompiles non-Bitcoin features out.all Orchard actions are streamed to and validated by the device
is_spendis required by firmware 7.15dummy shield/change actions use
is_spend=falseand receive no RedPallas signaturereal spends use
is_spend=trueZcashSignedPCZT.signaturesis compact: exactly one 64-byte signature per real spend, in ascending action orderan all-dummy shield transaction therefore returns zero Orchard signatures
shielded_pool/ironwood_digest(tags 19-20) andZCASH_SHIELDED_POOL_IRONWOODare schema only: RC18 is Orchard-only and rejects a non-Orchard pool or a presentironwood_digestwithFailure. The tags are allocated so they cannot be reused; no host should read them as supported behavior.These semantics are documented on the protocol fields and pinned by a machine-readable contract check.
Validation
6d0ae670e287a75338244fe82c4bef33a920a2eeReview and merge gates
No merge, tag, or release is requested before human review.